<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>LaTeX 公式转换器</title>
    <!--
    LaTeX 公式转换器
    作者：Zeakco
    日期：2025-03-11
    版本：1.0
    描述：该工具用于将文本中的 LaTeX 公式转换为 MathJax 可识别的格式，
        并提供 Markdown 和 PDF 格式的导出功能。
    -->
    <script>
        window.MathJax = {
            tex: { inlineMath: [['$', '$']] },
            output: { renderer: "svg" }
        };
    </script>
    <script src="MathJax/es5/tex-chtml.js"></script>
    <script src="JS/jspdf.umd.min.js"></script>
    <script src="JS/html2canvas.js"></script>
    <script src="JS/marked.min.js"></script>
    <script src="JS/html2pdf.bundle.min.js"></script>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            height: 100vh;
            background-color: #f4f4f4;
            padding: 20px;
            color: #333;
        }

        h1 {
            font-size: 32px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #222;
        }

        textarea,
        #outputText {
            width: 90%;
            max-width: 1000px;
            min-height: 200px;
            padding: 12px;
            border: 1px solid #ccc;
            border-radius: 8px;
            background-color: #fff;
            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
            color: #333;
            font-size: 16px;
            outline: none;
            transition: all 0.2s ease-in-out;
            overflow: auto;
            max-height: 80vh;
        }

        textarea:focus,
        #outputText:hover {
            border-color: #666;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
        }

        .btn-group {
            display: flex;
            gap: 12px;
            margin: 20px 0;
        }

        button {
            padding: 12px 20px;
            background-color: #222;
            color: #fff;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.3s;
            font-size: 14px;
        }

        button:hover {
            background-color: #444;
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
        }

        #outputText {
            background-color: #fff;
            color: #222;
            border: 1px solid #ccc;
        }
    </style> 
</head>

<body>
    <h1>LaTeX 公式转换器</h1>
    <textarea id="inputText" placeholder="在此粘贴包含公式的文本..."></textarea>
    <div class="btn-group">
        <button onclick="convertAndRender()">单独转换公式</button>
        <button onclick="convertByLine()">按行渲染</button>
        <button onclick="renderDirect()">直接渲染</button>
        <button onclick="renderMarkdownWithMath()">Markdown+公式渲染</button>
        <button onclick="exportMarkdown()">导出 Markdown</button>
        <button onclick="exportPDF()">导出 PDF</button>
    </div>
    <div id="outputText" style="white-space: pre-wrap;"></div>

    <script>
        let currentRenderMode = "formula"; // 记录当前渲染模式
        let convertedMarkdownText = ''; // 存储转换后的 Markdown 文本

        function convertAndRender() {
            currentRenderMode = "formula"; // 设置当前模式为单独转换公式
            const inputText = document.getElementById('inputText').value;
            const latexRegex = /\\[a-zA-Z]+(?:\{[^{}]*\})*/g;

            const convertedText = inputText.replace(latexRegex, match => `$${match}$`).replace(/\n/g, '<br>');
            convertedMarkdownText = inputText.replace(latexRegex, match => `$${match}$`); // 记录 Markdown 版本

            const outputDiv = document.getElementById('outputText');
            outputDiv.innerHTML = convertedText;

            if (window.MathJax) {
                MathJax.typesetPromise([outputDiv]);
            }
        }

        function convertByLine() {
            currentRenderMode = "line"; // 设置当前模式为按行渲染
            const inputText = document.getElementById('inputText').value;

            const convertedText = inputText
                .split('\n')
                .map(line => line.trim() ? `$${line}$` : '') // 仅对非空行加 $
                .join('<br>'); // 重新用 <br> 连接

            convertedMarkdownText = inputText
                .split('\n')
                .map(line => line.trim() ? `$${line}$` : '') // 仅对非空行加 $
                .join('\n'); // Markdown 版本使用 `\n` 连接，保留换行

            const outputDiv = document.getElementById('outputText');
            outputDiv.innerHTML = convertedText;

            if (window.MathJax) {
                MathJax.typesetPromise([outputDiv]);
            }
        }

        function renderDirect() {
            currentRenderMode = "direct";
            const inputText = document.getElementById('inputText').value;
            const outputDiv = document.getElementById('outputText');
            // 保留换行
            outputDiv.innerHTML = inputText.replace(/\n/g, '<br>');
            if (window.MathJax) {
                MathJax.typesetPromise([outputDiv]);
            }
            // 记录 Markdown 版本
            convertedMarkdownText = inputText;
        }

        function renderMarkdownWithMath() {
            currentRenderMode = "mdmath";
            let inputText = document.getElementById('inputText').value;
            // 去除所有包裹内容的反引号（`...` → ...）
            inputText = inputText.replace(/`([^`]+)`/g, '$1');
            const outputDiv = document.getElementById('outputText');
            // 用 marked 解析 Markdown
            outputDiv.innerHTML = marked.parse(inputText);
            // 用 MathJax 渲染公式
            if (window.MathJax) {
                MathJax.typesetPromise([outputDiv]);
            }
            convertedMarkdownText = inputText;
        }

        function exportMarkdown() {
            // 导出当前模式下的 Markdown 版本，只去除反引号，保留 $...$
            if (!convertedMarkdownText.trim()) {
                alert("内容为空，无法导出Markdown");
                return;
            }
            let exportText = convertedMarkdownText.replace(/`([^`]+)`/g, '$1');
            const blob = new Blob([exportText], { type: 'text/markdown' });
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = currentRenderMode === "line" ? 'exported_line_mode.md' : 'exported_formula_mode.md';
            link.click();
        }

        async function exportPDF() {
            if (!convertedMarkdownText.trim()) {
                alert("内容为空，无法导出PDF");
                return;
            }

            // 显示导出提示
            const exportTip = document.createElement('div');
            exportTip.id = 'exportPdfTip';
            exportTip.innerText = '正在导出PDF，请稍候...';
            Object.assign(exportTip.style, {
                position: 'fixed',
                top: '50%',
                left: '50%',
                transform: 'translate(-50%, -50%)',
                background: 'rgba(0,0,0,0.8)',
                color: '#fff',
                padding: '24px 48px',
                borderRadius: '12px',
                fontSize: '22px',
                zIndex: 9999
            });
            document.body.appendChild(exportTip);

            // 强制渲染提示
            await new Promise(resolve => setTimeout(resolve, 100));

            // 创建初始隐藏渲染容器
            let hiddenDiv = document.getElementById('hiddenPdfDiv');
            if (hiddenDiv) hiddenDiv.remove();  // 先删除旧的防止冲突
            hiddenDiv = document.createElement('div');
            hiddenDiv.id = 'hiddenPdfDiv';
            Object.assign(hiddenDiv.style, {
                position: 'absolute',
                top: '9999px',
                left: '0',
                width: '800px',
                padding: '20px',
                background: '#fff',
                color: '#000',
                fontSize: '32px',
                fontFamily: 'serif',
                lineHeight: '2.5',
                zIndex: '0',
                visibility: 'visible',
                overflow: 'visible'
            });
            // 增大所有内容和公式字号、行距
            const style = document.createElement('style');
            style.innerHTML = `
              #hiddenPdfDiv, #hiddenPdfDiv * {
                font-size: 32px !important;
                line-height: 2.5 !important;
              }
              #hiddenPdfDiv .mjx-chtml, 
              #hiddenPdfDiv .mjx-svg {
                font-size: 2.5em !important;
              }
            `;
            hiddenDiv.appendChild(style);
            document.body.appendChild(hiddenDiv);

            // 解析 Markdown 并渲染公式
            const mdText = convertedMarkdownText.replace(/`([^`]+)`/g, '$1');
            hiddenDiv.innerHTML = marked.parse(mdText);
            // 设置 MathJax 公式缩放为 200%
            if (window.MathJax) {
                MathJax.config = MathJax.config || {};
                MathJax.config.options = MathJax.config.options || {};
                MathJax.config.options.scale = 200; // 200% 放大
            }
            if (window.MathJax) {
                await MathJax.typesetPromise([hiddenDiv]);
                // 渲染后手动放大公式节点
                const mathNodes = hiddenDiv.querySelectorAll('.mjx-chtml, .mjx-svg');
                mathNodes.forEach(node => {
                    node.style.fontSize = '2.5em';
                });
            }

            const pdf = new jspdf.jsPDF('p', 'mm', 'a4');
            const pdfWidth = pdf.internal.pageSize.getWidth();
            const pdfHeight = pdf.internal.pageSize.getHeight();
            const pxPerMm = 96 / 25.4;
            const pageHeightPx = pdfHeight * pxPerMm;
            const margin = 15; // 左右页边距，单位mm

            // 创建分页容器，只创建一次，重用
            let pageContainer = document.getElementById('pageContainer');
            if (pageContainer) pageContainer.remove();
            pageContainer = document.createElement('div');
            Object.assign(pageContainer.style, {
                width: hiddenDiv.clientWidth + 'px',
                position: 'relative',
                background: '#fff',
                overflow: 'visible'
            });
            document.body.appendChild(pageContainer);

            // 先克隆所有子元素，避免修改原hiddenDiv
            const allElements = Array.from(hiddenDiv.children).map(el => el.cloneNode(true));

            // 将所有元素放入一个临时容器，方便操作
            const tempContainer = document.createElement('div');
            allElements.forEach(el => tempContainer.appendChild(el));
            tempContainer.style.position = 'absolute';
            tempContainer.style.top = '9999px';
            tempContainer.style.left = '0';
            tempContainer.style.width = hiddenDiv.clientWidth + 'px';
            tempContainer.style.visibility = 'hidden';
            document.body.appendChild(tempContainer);

            // 分页逻辑，动态拼接元素到 pageContainer
            let currentPageHeight = 0;
            let currentPageElements = [];

            async function renderPage(elements) {
                pageContainer.innerHTML = '';
                elements.forEach(el => pageContainer.appendChild(el));
                await MathJax.typesetPromise([pageContainer]);

                const canvas = await html2canvas(pageContainer, {
                    scale: 4,
                    useCORS: true,
                    backgroundColor: '#fff',
                    width: pageContainer.clientWidth,
                    height: pageContainer.clientHeight
                });

                const imgData = canvas.toDataURL('image/jpeg', 1.0);
                const imgProps = pdf.getImageProperties(imgData);
                const imgWidth = pdfWidth - 2 * margin;
                const imgHeight = (imgProps.height * imgWidth) / imgProps.width;

                if (pdf.internal.getNumberOfPages() > 0) pdf.addPage();
                pdf.addImage(imgData, 'JPEG', margin, 0, imgWidth, imgHeight);
            }

            for (let i = 0; i < allElements.length; i++) {
                const el = allElements[i];
                const elHeight = el.getBoundingClientRect().height;

                // 处理单个元素比一页还高，强制单独分页
                if (elHeight > pageHeightPx) {
                    if (currentPageElements.length > 0) {
                        await renderPage(currentPageElements);
                        currentPageElements = [];
                        currentPageHeight = 0;
                    }
                    // 单独渲染这个大元素
                    await renderPage([el]);
                    continue;
                }

                if (currentPageHeight + elHeight > pageHeightPx) {
                    // 当前页满了，渲染已收集内容
                    await renderPage(currentPageElements);
                    // 新页开始
                    currentPageElements = [el];
                    currentPageHeight = elHeight;
                } else {
                    currentPageElements.push(el);
                    currentPageHeight += elHeight;
                }
            }

            if (currentPageElements.length > 0) {
                await renderPage(currentPageElements);
            }

            // 清理
            tempContainer.remove();
            pageContainer.remove();
            hiddenDiv.remove();

            // 删除第一页（通常是空白页）
            if (pdf.internal.getNumberOfPages() > 1) {
                pdf.deletePage(1);
            }

            pdf.save('exported_markdown.pdf');

            // 关闭导出提示
            if (exportTip && exportTip.parentNode) {
                exportTip.parentNode.removeChild(exportTip);
            }
        }



    </script>
</body>

</html>